package org.bronycorps.mlptss.manager;

import java.util.ArrayList;
import java.util.List;

import org.bronycorps.mlptss.domain.actor.BonusActor;
import org.bronycorps.mlptss.domain.actor.BulletActor;
import org.bronycorps.mlptss.domain.actor.EnemyActor;
import org.bronycorps.mlptss.domain.actor.MissileActor;
import org.bronycorps.mlptss.domain.actor.PlayerActor;
import org.bronycorps.mlptss.domain.actor.SpecialBulletActor;
import org.bronycorps.mlptss.domain.actor.SpecialMissileActor;
import org.bronycorps.mlptss.domain.controller.GameController;
import org.bronycorps.mlptss.domain.layer.ActorLayer;
import org.bronycorps.mlptss.domain.layer.MissileLayer;
import org.bronycorps.mlptss.domain.layer.Track;
import org.bronycorps.mlptss.domain.model.Bonus;
import org.bronycorps.mlptss.domain.model.Bullet;
import org.bronycorps.mlptss.domain.model.Enemy;
import org.bronycorps.mlptss.domain.model.Missile;
import org.bronycorps.mlptss.domain.model.Player;
import org.bronycorps.mlptss.domain.model.SpecialMissile;
import org.bronycorps.mlptss.domain.model.event.CreateMissileEvent;

import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector2;

public class CollisionManager {

	private static final float COMBO_COOLDOWN = 1;
	private static final float EXPLOSION_RADIUS_RATIO = 5.0f;
	private static CollisionManager instance = null;

	public static CollisionManager getInstance() {
		if (instance == null) {
			instance = new CollisionManager();
		}
		return instance;
	}

	public static CollisionManager getInstance(ActorLayer actorLayer,
			MissileLayer missileLayer) {
		if (instance == null) {
			instance = new CollisionManager();
		}
		instance.actorLayer = actorLayer;
		instance.missileLayer = missileLayer;
		return instance;
	}

	private ActorLayer actorLayer;
	private int bulletEnemyPoints;
	private int bulletMissilePoints;
	private int comboMissileLimit;
	private MissileLayer missileLayer;

	private CollisionManager() {
		super();

		this.comboMissileLimit = 3;
		this.bulletMissilePoints = 20;
		this.bulletEnemyPoints = 10;
	}

	public void act(float delta) {
		checkCollisions();
	}

	public void chainExplode(EnemyActor enemyActor) {
		if (GameController.getInstance().isMatchOver()) {
			return;
		}

		Track currentTrack = enemyActor.getTrack();
		Vector2 center = enemyActor.getModel().getPosition();
		float radius = enemyActor.getHitBoxRadius() * EXPLOSION_RADIUS_RATIO;
		Player player = (Player) currentTrack.getPlayer().getModel();

		// check every other enemy
		for (EnemyActor enemy : currentTrack.getEnemies()) {
			Enemy enemyModel = (Enemy) enemy.getModel();

			if (enemyModel.getLife() > 0) {
				float dx = enemyModel.getPosition().x - center.x;
				float dy = enemyModel.getPosition().y - center.y;
				if (Math.sqrt(dx * dx + dy * dy) < enemy.getHitBoxRadius()
						+ radius) {
					// chain explode
					enemyModel.hit((Enemy) enemyActor.getModel());

					if (enemyModel.isExploding()
							&& currentTrack.getComboCooldown() > 0) {
						currentTrack.addComboMultiplier(1);
						player.addHighscore(10 * currentTrack
								.getComboMultiplier());
						player.addMaxSpecialAttackCharge(0.05f);

						if (currentTrack.getComboMultiplier() > this.comboMissileLimit) {
							// create missile
							currentTrack
									.fireCreateMissileEvent(new CreateMissileEvent(
											enemyActor, currentTrack));
						}
					}
					currentTrack.setComboCooldown(COMBO_COOLDOWN);
				}
			}
		}

		// check for missiles
		Vector2 globalPosition = new Vector2(0, enemyActor.getTrack().getY())
				.add(center);
		// create a copy of the list to avoid concurrent access/modification of
		// the list (when missiles are chain created
		List<MissileActor> missiles = new ArrayList<MissileActor>(
				this.missileLayer.getMissiles());

		for (MissileActor missile : missiles) {
			Missile missileModel = (Missile) missile.getModel();
			if (missileModel.getState() == Missile.State.NORMAL
					&& missileModel.getOwner() != currentTrack.getPlayer()
							.getModel()) {
				float dx = missileModel.getPosition().x - globalPosition.x;
				float dy = missileModel.getPosition().y - globalPosition.y;
				if (Math.sqrt(dx * dx + dy * dy) < missile.getHitBoxRadius()
						+ radius) {
					// chain explode
					missileModel.hit((Enemy) enemyActor.getModel());

					if (currentTrack.getComboCooldown() > 0) {
						currentTrack.addComboMultiplier(1);
						((Player) currentTrack.getPlayer().getModel())
								.addHighscore(10 * currentTrack
										.getComboMultiplier());
					}
					currentTrack.setComboCooldown(COMBO_COOLDOWN);

					// create counter missile
					currentTrack.fireCreateMissileEvent(new CreateMissileEvent(
							missile, currentTrack));
				}
			}
		}

	}

	public void checkCollisions() {
		if (this.actorLayer == null) {
			throw new RuntimeException("CollisionManager not ready");
		}

		List<MissileActor> missiles = new ArrayList<MissileActor>(
				this.missileLayer.getMissiles());

		List<SpecialMissileActor> specialMissiles = this.missileLayer
				.getSpecialMissiles();

		for (Track currentTrack : this.actorLayer.getTracks()) {
			final List<EnemyActor> enemies = currentTrack.getEnemies();
			final List<BulletActor> bullets = currentTrack.getBullets();
			List<SpecialBulletActor> specialBullets = currentTrack
					.getSpecialBullets();

			final PlayerActor player = currentTrack.getPlayer();
			final Player playerModel = (Player) player.getModel();
			final Rectangle playerBounds = player.getHitBox();

			// check for bonus
			if (currentTrack.getBonusActor() != null) {
				BonusActor bonusActor = currentTrack.getBonusActor();
				Rectangle bonusHitbox = bonusActor.getHitBox();
				Rectangle dbg = new Rectangle(bonusHitbox);
				dbg.y += 300;
				if (bonusHitbox.overlaps(playerBounds)) {
					playerModel.addBonus((Bonus) bonusActor.getModel());

					currentTrack.removeBonus();
				}
			}

			// check for enemies
			for (EnemyActor enemyActor : enemies) {
				Enemy enemyModel = (Enemy) enemyActor.getModel();
				if (enemyModel.isExploding()) {
					continue;
				}
				final Rectangle enemyBounds = enemyActor.getHitBox();

				if (playerModel.getState() == Player.State.NORMAL
						|| playerModel.getState() == Player.State.WEAK) {
					// check collision with player
					if (enemyBounds.overlaps(playerBounds)) {
						playerModel.hit(enemyModel);
						break;
					}
				}

				// check collision with bullets
				for (BulletActor bulletActor : bullets) {
					if (enemyBounds.overlaps(bulletActor.getHitBox())) {
						enemyModel.hit((Bullet) bulletActor.getModel());

						bulletActor.setAlive(false);
						if (enemyModel.isExploding()) {

							playerModel.addHighscore(this.bulletEnemyPoints);
							playerModel.addMaxSpecialAttackCharge(0.05f);
							currentTrack.addComboMultiplier(1);
							currentTrack.setComboCooldown(COMBO_COOLDOWN);
							break;
						}
					}
				}

				for (SpecialBulletActor specialBulletActor : specialBullets) {
					if (enemyBounds.overlaps(specialBulletActor.getHitBox())) {
						Boolean explodeOnHit = specialBulletActor.getBullet()
								.getModel().explodeOnHit();
						enemyModel.terminate(explodeOnHit);
						if (explodeOnHit) {
							specialBulletActor.setAlive(false);
						}

					}
				}
			}

			// check for missiles
			for (MissileActor missileActor : missiles) {

				if (!missileActor.getTrack().equals(currentTrack)) {
					// ignore missiles for the other track
					continue;
				}

				final Missile missile = (Missile) missileActor.getModel();

				Rectangle missileBounds = new Rectangle(
						missileActor.getHitBox());
				missileBounds.y -= currentTrack.getY();

				// check collision with player
				if (playerModel.getState() == Player.State.NORMAL
						|| playerModel.getState() == Player.State.WEAK) {
					// check collision with player
					if (missileBounds.overlaps(playerBounds)) {
						playerModel.hit((Missile) missileActor.getModel());
						break;
					}
				}

				// check collision with bullets
				for (BulletActor bulletActor : bullets) {
					if (missileBounds.overlaps(bulletActor.getHitBox())) {
						missile.hit((Bullet) bulletActor.getModel());

						playerModel.addHighscore(this.bulletMissilePoints);
						bulletActor.terminate();

						if (missile.getState() == Missile.State.EXPLODE
								&& missile.getOwner() != playerModel) {
							currentTrack
									.fireCreateMissileEvent(new CreateMissileEvent(
											missileActor, currentTrack));
						}
						break;
					}
				}

				// check collision with special attack
				for (SpecialBulletActor specialBulletActor : specialBullets) {
					if (missileBounds.overlaps(specialBulletActor.getHitBox())) {
						missileActor.terminate();
						currentTrack
								.fireCreateMissileEvent(new CreateMissileEvent(
										missileActor, currentTrack));
					}
				}
			}

			// check for special missiles
			for (SpecialMissileActor specialMissileActor : specialMissiles) {

				if (!specialMissileActor.getTrack().equals(currentTrack)) {
					// ignore missiles for the other track
					continue;
				}

				Rectangle missileBounds = new Rectangle(
						specialMissileActor.getHitBox());
				missileBounds.y -= currentTrack.getY();

				// check collision with player
				if (playerModel.getState() == Player.State.NORMAL
						|| playerModel.getState() == Player.State.WEAK) {
					// check collision with player
					if (missileBounds.overlaps(playerBounds)) {
						playerModel.hit((SpecialMissile) specialMissileActor
								.getModel());
						break;
					}
				}
			}
		}

	}

	public ActorLayer getActorLayer() {
		return this.actorLayer;
	}

	public MissileLayer getMissileLayer() {
		return this.missileLayer;
	}

	public void setActorLayer(ActorLayer actorLayer) {
		this.actorLayer = actorLayer;
	}

	public void setMissileLayer(MissileLayer missileLayer) {
		this.missileLayer = missileLayer;
	}

}
